package labox.innovation.gameserver;

import java.io.BufferedOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javolution.util.FastMap;
import labox.innovation.config.Config;
import labox.innovation.gameserver.network.L2GameClient;
import labox.innovation.gameserver.network.L2GameClient.GameClientState;
import labox.innovation.gameserver.network.gameserverpackets.AuthRequest;
import labox.innovation.gameserver.network.gameserverpackets.ChangeAccessLevel;
import labox.innovation.gameserver.network.gameserverpackets.CharacterCountsPacket;
import labox.innovation.gameserver.network.gameserverpackets.GameServerBasePacket;
import labox.innovation.gameserver.network.gameserverpackets.PlayerAuthRequest;
import labox.innovation.gameserver.network.gameserverpackets.PlayerCountsPacket;
import labox.innovation.gameserver.network.gameserverpackets.PlayerInGame;
import labox.innovation.gameserver.network.gameserverpackets.PlayerLogout;
import labox.innovation.gameserver.network.gameserverpackets.ServerStatus;
import labox.innovation.gameserver.network.loginserverpackets.AuthResponse;
import labox.innovation.gameserver.network.loginserverpackets.InitLS;
import labox.innovation.gameserver.network.loginserverpackets.KickPlayer;
import labox.innovation.gameserver.network.loginserverpackets.LoginServerFail;
import labox.innovation.gameserver.network.loginserverpackets.PlayerAuthResponse;
import labox.innovation.gameserver.network.serverpackets.CharSelectionInfo;
import labox.innovation.gameserver.network.serverpackets.LoginFail;
import labox.innovation.util.Rnd;
import labox.innovation.util.Util;
import labox.innovation.util.crypt.NewCrypt;

public class LoginServerThread extends Thread {

	protected static final Logger _log = LoggerFactory.getLogger(LoginServerThread.class.getName());

	/** {@see labox.innovation.loginserver.LoginServer#PROTOCOL_REV } */
	private static final int REVISION = 0x0102;

	private String _hostname;

	private int _port;

	private int _gamePort;

	private Socket _loginSocket;

	private InputStream _in;

	private OutputStream _out;

	private boolean _acceptAlternate;

	/**
	 * 游戏服务器请求的ID
	 */
	private int _requestID;

	/**
	 * 游戏服务器最终得到的服务器ID
	 */
	private int _serverID;

	private int _maxPlayer;

	/**
	 * 等待登陆服务器发回授权验证的客户端对象表
	 */
	private Map<String, WaitingClient> _waitingClients;

	/**
	 * 记录进入游戏服务器的账户表
	 */
	private Map<String, L2GameClient> _accountsInGameServer;

	/**
	 * 当前的服务器状态
	 */
	private int _status;

	private String _serverName;

	private String _gameExternalHost;

	private String _gameInternalHost;

	private boolean _run;

	private LoginServerThread() {
		super("LoginServerThread");
		_port = Config.GAME_SERVER_LOGIN_PORT;
		_gamePort = Config.PORT_GAME;
		_hostname = Config.GAME_SERVER_LOGIN_HOST;
		if ("*".equals(_hostname)) {
			_hostname = "127.0.0.1";
		}

		_requestID = Config.REQUEST_ID;
		_acceptAlternate = Config.ACCEPT_ALTERNATE_ID;
		_gameExternalHost = Config.EXTERNAL_HOSTNAME;
		_gameInternalHost = Config.INTERNAL_HOSTNAME;
		_waitingClients = new FastMap<String, WaitingClient>().shared();
		_accountsInGameServer = new FastMap<String, L2GameClient>().shared();
		_maxPlayer = Config.MAXIMUM_ONLINE_USERS;
		_run = true;
	}

	public static LoginServerThread getInstance() {
		return SingletonHolder._instance;
	}

	@Override
	public void run() {
		while (_run) {
			int lengthHi = 0;
			int lengthLo = 0;
			int length = 0;
			boolean checksumOk = false;
			try {
				// Connection
				_log.info("连接登陆服务器:Connecting to login on " + _hostname + ":" + _port, _requestID, _serverName);
				_loginSocket = new Socket(_hostname, _port);
				_in = _loginSocket.getInputStream();
				_out = new BufferedOutputStream(_loginSocket.getOutputStream());

				//-- 防止本地游戏服务器在登录服务器中注册"*"
				if ("*".equals(_gameExternalHost)) {
					_gameExternalHost = _loginSocket.getLocalAddress().getHostAddress();

				}

				if ("*".equals(_gameInternalHost)) {
					_gameInternalHost = _loginSocket.getLocalAddress().getHostAddress();
				}

				while (true) {
					lengthLo = _in.read();
					if (lengthLo < 0) {
						_log.info("登陆服务器连接中断:Login terminated the connection.", _requestID, _serverName);
						break;
					}
					lengthHi = _in.read();
					length = (lengthHi << 8) + lengthLo;

					byte[] data = new byte[length - 2];

					int n = 0;
					while (n < data.length) {
						int count = _in.read(data, n, data.length - n);
						if (count < 0)
							throw new EOFException();
						n += count;
					}

					checksumOk = NewCrypt.verifyChecksum(data);

					if (!checksumOk) {
						_log.warn("错误的校验:Incorrect packet checksum, ignoring packet (LS)", _requestID, _serverName);
						break;
					}

					int packetType = data[0] & 0xff;

					switch (packetType) {
					case 00: //-- 接受LS上首次发送过来的初始包，包含版本信息、公钥信息等。
						InitLS init = new InitLS(data);
						if (_log.isDebugEnabled())
							_log.debug("Init received");

						if (init.getRevision() != REVISION) { //-- 协议版本比对
							_log.warn("协议版本不匹配:Revision mismatch between LS and GS", _requestID, _serverName);
							break;
						}

						AuthRequest ar = new AuthRequest(_requestID, _acceptAlternate, Config.GAME_HOST_NAME, _gameExternalHost, _gameInternalHost, _gamePort, _maxPlayer, Config.SERVER_TYPE);
						sendPacket(ar);
						if (_log.isDebugEnabled())
							_log.debug("Sent AuthRequest to login");
						break;
					case 01://向登陆服务器的注册失败了
						LoginServerFail lsf = new LoginServerFail(data);
						_log.info("注册服务器失败:" + lsf.getReasonString(), _requestID, _serverName);
						// login will close the connection here
						break;
					case 02:
						AuthResponse aresp = new AuthResponse(data);
						_serverID = aresp.getServerId();
						_serverName = aresp.getServerName();
						//注意这个时候才拿到真正的游戏服务器ID
						Config.SERVER_ID = _serverID;

						_log.info("注册服务器成功:" + _serverID + " : " + _serverName, _serverID, _serverName);
						ServerStatus st = new ServerStatus();
						if (Config.SERVER_LIST_BRACKET) {
							st.addAttribute(ServerStatus.SERVER_LIST_SQUARE_BRACKET, ServerStatus.ON);
						} else {
							st.addAttribute(ServerStatus.SERVER_LIST_SQUARE_BRACKET, ServerStatus.OFF);
						}
						if (Config.SERVER_LIST_CLOCK) {
							st.addAttribute(ServerStatus.SERVER_LIST_CLOCK, ServerStatus.ON);
						} else {
							st.addAttribute(ServerStatus.SERVER_LIST_CLOCK, ServerStatus.OFF);
						}
						if (Config.SERVER_LIST_TESTSERVER) {
							st.addAttribute(ServerStatus.TEST_SERVER, ServerStatus.ON);
						} else {
							st.addAttribute(ServerStatus.TEST_SERVER, ServerStatus.OFF);
						}
						if (Config.SERVER_GMONLY) {
							st.addAttribute(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_GM_ONLY);
							_status = ServerStatus.STATUS_GM_ONLY;
						} else {
							st.addAttribute(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_GOOD);
							_status = ServerStatus.STATUS_GOOD;
						}

						sendPacket(st);

						//有时会出现登陆服务器关闭或异常的情况，当重启登陆服务器后，登陆服务器上没有每个游戏服务器上玩家的账户情况这里需要发给他
						if (_accountsInGameServer.size() > 0) {
							PlayerInGame pig = new PlayerInGame(_accountsInGameServer.keySet());
							sendPacket(pig);
						}
						break;
					case 03:
						PlayerAuthResponse par = new PlayerAuthResponse(data);
						String account = par.getAccount();

						WaitingClient wcToRemove = _waitingClients.get(account);

						if (wcToRemove != null) {
							if (par.isAuthed()) {
								if (_log.isDebugEnabled())
									_log.debug("Login accepted player " + wcToRemove.account + " waited(" + (GameTimeController.getGameTicks() - wcToRemove.timestamp) + "ms)");
								//
								sendPacket(new PlayerInGame(par.getAccount()));

								final WaitingClient waitClient = wcToRemove;
								final String channelId = par.getChannelId();
								ThreadPoolManager.getInstance().execute(new Runnable() {
									public void run() {
										waitClient.gameClient.setState(GameClientState.AUTHED);//设置客户端进入已授权状态
										waitClient.gameClient.setSessionId(waitClient.session);
										waitClient.gameClient.setChannelId(channelId);
										CharSelectionInfo cl = new CharSelectionInfo(waitClient.account, waitClient.gameClient.getSessionId());
										waitClient.gameClient.getConnection().sendPacket(cl);//发送给客户端角色选择状态
										waitClient.gameClient.setCharSelection(cl.getCharInfo());//设置客户端的选择位置map
									}
								});

								//根据服务器的配置情况，修改登录服务器上此游戏服务器的状态
								if (_status == ServerStatus.STATUS_GOOD && _accountsInGameServer.size() >= getMaxPlayer() / 2) {
									setServerStatus(ServerStatus.STATUS_NORMAL);
								} else if (_status == ServerStatus.STATUS_NORMAL && _accountsInGameServer.size() >= getMaxPlayer()) {
									setServerStatus(ServerStatus.STATUS_FULL);
								}
							} else {
								_log.warn("会话键值错误:session key is not correct. closing connection.IP:" + wcToRemove.gameClient.getConnection().getInetAddress().getHostAddress(), _serverID, _serverName);
								wcToRemove.gameClient.closeNow();
							}
							_waitingClients.remove(account);
						}
						break;
					case 04:
						KickPlayer kp = new KickPlayer(data);
						doKickPlayer(kp.getAccount());
						break;
					case 0x05:
						//-- 接受登录服务器发送检测包
						break;
					default:
						break;
					}
				}
			} catch (UnknownHostException e) {
				e.printStackTrace();
			} catch (IOException e) {
				_log.error("Deconnected from Login, Trying to reconnect:", _serverID, e);
				e.printStackTrace();
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				try {
					_in.close();
					_out.close();
					_loginSocket.close();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}

			try {
				Thread.sleep(5000L); // 5 seconds tempo.
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	public void close() {
		_run = false;
		interrupt();
	}

	/**
	 * 进入一个等待验证的客户端，并向登陆服务器发送此账户的验证授权
	 * 
	 * @param acc
	 * @param client
	 * @param key
	 */
	public void addWaitingClientAndSendRequest(String acc, L2GameClient client, int key) {
		WaitingClient wc = new WaitingClient(acc, client, key);
		_waitingClients.put(acc, wc);
		PlayerAuthRequest par = new PlayerAuthRequest(acc, key);
		try {
			sendPacket(par);
		} catch (IOException e) {
			_log.error("Error while sending player auth request", _serverID, e);
		}
	}

	public void sendLogout(String account) {
		PlayerLogout pl = new PlayerLogout(account);
		try {
			sendPacket(pl);
		} catch (IOException e) {
			_log.error("Error while sending logout packet to login", _serverID, e);
		} finally {
			//如论如何要删除掉此账户对应的客户端模型对象，防止内存泄露
			_accountsInGameServer.remove(account);
			_log.info("账户登出:", Config.SERVER_ID, account);

			if (_status == ServerStatus.STATUS_NORMAL && _accountsInGameServer.size() < getMaxPlayer() / 2) {
				setServerStatus(ServerStatus.STATUS_GOOD);
			} else if (_status == ServerStatus.STATUS_FULL && _accountsInGameServer.size() < getMaxPlayer()) {
				setServerStatus(ServerStatus.STATUS_NORMAL);
			}
		}
	}

	/**
	 * 加入进入游戏服务器的表
	 * 
	 * @param account
	 * @param client
	 */
	public void addGameServerLogin(String account, L2GameClient client) {
		_accountsInGameServer.put(account, client);
		_log.info("账户登入:", Config.SERVER_ID, account);
	}

	public void sendAccessLevel(String account, int level) {
		ChangeAccessLevel cal = new ChangeAccessLevel(account, level);
		try {
			sendPacket(cal);
		} catch (IOException e) {
			_log.error("Error while sending AccessLevel packet", _serverID, e);
		}
	}

	/**
	 * 踢掉一个进入游戏服务器的账户
	 * 
	 * @param account
	 */
	public void doKickPlayer(String account) {
		if (_accountsInGameServer.get(account) != null) {
			_accountsInGameServer.get(account).sendPacket(new LoginFail(LoginFail.ACOUNT_ALREADY_IN_USE));
			_accountsInGameServer.get(account).closeNow();//由网络连接关闭事件的回调去触发sendLogout，这里就不在发了
		}
	}

	/**
	 * 获得在游戏服务器中账户的数量
	 * 
	 * @return
	 */
	public int getAccountsInGameServerCount() {
		return _accountsInGameServer.size();
	}

	public static byte[] generateHex(int size) {
		byte[] array = new byte[size];
		Rnd.nextBytes(array);
		if (_log.isDebugEnabled())
			_log.debug("Generated random String:  \"" + array + "\"");
		return array;
	}

	/**
	 * @param sl
	 * @throws IOException
	 */
	private void sendPacket(GameServerBasePacket sl) throws IOException {

		byte[] data = sl.getContent();
		//加入校验位
		NewCrypt.appendChecksum(data);
		if (_log.isDebugEnabled())
			_log.debug("[S]\n" + Util.printData(data));

		int len = data.length + 2;
		synchronized (_out) //必须同步，避免多个线程同时写
		{
			_out.write(len & 0xff);
			_out.write(len >> 8 & 0xff);
			_out.write(data);
			_out.flush();
		}
	}

	/**
	 * @param maxPlayer
	 *            The maxPlayer to set.
	 */
	public void setMaxPlayer(int maxPlayer) {
		sendServerStatus(ServerStatus.MAX_PLAYERS, maxPlayer);
		_maxPlayer = maxPlayer;
	}

	/**
	 * @return Returns the maxPlayer.
	 */
	public int getMaxPlayer() {
		return _maxPlayer;
	}

	/**
	 * @param server_gm_only
	 */
	public void sendServerStatus(int id, int value) {
		ServerStatus ss = new ServerStatus();
		ss.addAttribute(id, value);
		try {
			sendPacket(ss);
		} catch (IOException e) {
			if (Config.DEBUG)
				e.printStackTrace();
		}
	}

	/**
	 * 向登陆服务器发送当期游戏服务器玩家数量
	 */
	public void sendGameServerPlayerCounts() {
		PlayerCountsPacket playerCountsPacket = new PlayerCountsPacket();
		try {
			sendPacket(playerCountsPacket);
		} catch (Exception e) {
			if (Config.DEBUG)
				e.printStackTrace();
		}
	}

	/**
	 * 向登陆服务器发送当前玩家在当前游戏服上创建角色数
	 * 
	 * @param account
	 */
	public void sendCharacterCounts(String account) {
		CharacterCountsPacket characterCountsPacket = new CharacterCountsPacket(account);
		try {
			sendPacket(characterCountsPacket);
		} catch (Exception e) {
			if (Config.DEBUG)
				e.printStackTrace();
		}
	}

	/**
	 * @return
	 */
	public String getStatusString() {
		return ServerStatus.STATUS_STRING[_status];
	}

	/**
	 * @return
	 */
	public boolean isClockShown() {
		return Config.SERVER_LIST_CLOCK;
	}

	/**
	 * @return
	 */
	public boolean isBracketShown() {
		return Config.SERVER_LIST_BRACKET;
	}

	/**
	 * @return Returns the serverName.
	 */
	public String getServerName() {
		return _serverName;
	}

	public void setServerStatus(int status) {
		switch (status) {
		case ServerStatus.STATUS_DOWN:
			sendServerStatus(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_DOWN);
			_status = status;
			break;
		case ServerStatus.STATUS_FULL:
			sendServerStatus(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_FULL);
			_status = status;
			break;
		case ServerStatus.STATUS_GM_ONLY:
			sendServerStatus(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_GM_ONLY);
			_status = status;
			break;
		case ServerStatus.STATUS_GOOD:
			sendServerStatus(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_GOOD);
			_status = status;
			break;
		case ServerStatus.STATUS_NORMAL:
			sendServerStatus(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_NORMAL);
			_status = status;
			break;
		default:
			throw new IllegalArgumentException("Status does not exists:" + status);
		}
	}

	private static class WaitingClient {
		public int timestamp;

		public String account;

		public L2GameClient gameClient;

		public int session;

		public WaitingClient(String acc, L2GameClient client, int key) {
			account = acc;
			timestamp = GameTimeController.getGameTicks();
			gameClient = client;
			session = key;
		}
	}

	@SuppressWarnings("synthetic-access")
	private static class SingletonHolder {
		protected static final LoginServerThread _instance = new LoginServerThread();
	}
}
